/// @file db_ao.cpp
/// @brief Implementation of the DbAo class
///
/// This file implements the DbAo class which handles all database operations
/// and communicates with the AuthAo through events.

#include "db_ao.h"
#include "auth_events.h"
#include "spdlog/spdlog.h"
#include <cstdint>

namespace
{
Q_DEFINE_THIS_FILE
}

// Global instance of the DbAo
DbAo* const the_AO_Db = new DbAo("auth.db");

DbAo::DbAo(const std::string& db_path) : QActive(initial), db_manager_(nullptr), user_repo_(nullptr), session_repo_(nullptr)
{
    // 不在构造函数中调用 spdlog，避免初始化时的问题
    // 数据库管理器将在 initializeDatabase 中初始化
}

DbAo::~DbAo()
{
    delete session_repo_;
    delete user_repo_;
    delete db_manager_;
}

Q_STATE_DEF(DbAo, initial)
{
    Q_UNUSED_PAR(e);

    // Subscribe to database request events
    subscribe(DB_USER_FIND_BY_USERNAME_SIG);
    subscribe(DB_USER_CREATE_SIG);
    subscribe(DB_SESSION_CREATE_SIG);
    subscribe(DB_SESSION_REMOVE_SIG);
    subscribe(DB_SESSION_FIND_BY_USER_ID_SIG);

    // Initialize database
    initializeDatabase();

    return tran(&processing);
}

Q_STATE_DEF(DbAo, processing)
{
    QP::QState status_;

    switch (e->sig)
    {
        case DB_USER_FIND_BY_USERNAME_SIG:
        {
            const DbUserFindByUsernameRequestEvt* userFindEvt = Q_EVT_CAST(DbUserFindByUsernameRequestEvt);
            spdlog::info("DB AO: Processing user find request for username '{}'", userFindEvt->username);
            processUserFindByUsernameRequest(userFindEvt);
            status_ = Q_RET_HANDLED;
            break;
        }
        case DB_USER_CREATE_SIG:
        {
            const DbUserCreateRequestEvt* userCreateEvt = Q_EVT_CAST(DbUserCreateRequestEvt);
            spdlog::info("DB AO: Processing user create request for username '{}'", userCreateEvt->username);
            processUserCreateRequest(userCreateEvt);
            status_ = Q_RET_HANDLED;
            break;
        }
        case DB_SESSION_CREATE_SIG:
        {
            const DbSessionCreateRequestEvt* sessionCreateEvt = Q_EVT_CAST(DbSessionCreateRequestEvt);
            spdlog::info("DB AO: Processing session create request for user ID {}", sessionCreateEvt->user_id);
            processSessionCreateRequest(sessionCreateEvt);
            status_ = Q_RET_HANDLED;
            break;
        }
        case DB_SESSION_REMOVE_SIG:
        {
            const DbSessionRemoveRequestEvt* sessionRemoveEvt = Q_EVT_CAST(DbSessionRemoveRequestEvt);
            spdlog::info("DB AO: Processing session remove request for user ID {}", sessionRemoveEvt->user_id);
            processSessionRemoveRequest(sessionRemoveEvt);
            status_ = Q_RET_HANDLED;
            break;
        }
        case DB_SESSION_FIND_BY_USER_ID_SIG:
        {
            const DbSessionFindByUserIdRequestEvt* sessionFindEvt = Q_EVT_CAST(DbSessionFindByUserIdRequestEvt);
            spdlog::info("DB AO: Processing session find request for user ID {}", sessionFindEvt->user_id);
            processSessionFindByUserIdRequest(sessionFindEvt);
            status_ = Q_RET_HANDLED;
            break;
        }
        default:
        {
            status_ = super(&top);
            break;
        }
    }
    return status_;
}

void DbAo::processUserFindByUsernameRequest(const DbUserFindByUsernameRequestEvt* const evt)
{
    if (!user_repo_)
    {
        spdlog::error("DB AO: User repository not initialized");
        // Post DB_USER_FIND_RESPONSE_FAILURE event to Auth AO
        #ifdef QEVT_PAR_INIT
        #ifdef QEVT_PAR_INIT
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_USER_FIND_RESPONSE_FAILURE_SIG), this);
        #else
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_USER_FIND_RESPONSE_FAILURE_SIG), this);
        #endif
#else
        #ifdef QEVT_PAR_INIT
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_USER_FIND_RESPONSE_FAILURE_SIG), this);
        #else
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_USER_FIND_RESPONSE_FAILURE_SIG), this);
        #endif
#endif
        return;
    }

    // Find user by username
    auto user = user_repo_->findByUsername(evt->username);

    if (user.has_value())
    {
        // Post DB_USER_FIND_RESPONSE_SUCCESS event to Auth AO
#ifdef QEVT_PAR_INIT
        DbUserFindResponseSuccessEvt* responseEvt = Q_NEW(DbUserFindResponseSuccessEvt, DB_USER_FIND_RESPONSE_SUCCESS_SIG,
                                                          user->id, user->username, user->password, user->created_at, user->updated_at);
#else
        DbUserFindResponseSuccessEvt* responseEvt = Q_NEW(DbUserFindResponseSuccessEvt, DB_USER_FIND_RESPONSE_SUCCESS_SIG);
        responseEvt->user_id                      = user->id;
        responseEvt->username                     = user->username;
        responseEvt->password                     = user->password;
        responseEvt->created_at                   = user->created_at;
        responseEvt->updated_at                   = user->updated_at;
#endif
        QP::QActive::PUBLISH(responseEvt, this);
    }
    else
    {
        // Post DB_USER_FIND_RESPONSE_FAILURE event to Auth AO
        #ifdef QEVT_PAR_INIT
        #ifdef QEVT_PAR_INIT
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_USER_FIND_RESPONSE_FAILURE_SIG), this);
        #else
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_USER_FIND_RESPONSE_FAILURE_SIG), this);
        #endif
#else
        #ifdef QEVT_PAR_INIT
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_USER_FIND_RESPONSE_FAILURE_SIG), this);
        #else
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_USER_FIND_RESPONSE_FAILURE_SIG), this);
        #endif
#endif
    }
}

void DbAo::processUserCreateRequest(const DbUserCreateRequestEvt* const evt)
{
    if (!user_repo_)
    {
        spdlog::error("DB AO: User repository not initialized");
        // Post DB_USER_CREATE_RESPONSE_FAILURE event to Auth AO
        #ifdef QEVT_PAR_INIT
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_USER_CREATE_RESPONSE_FAILURE_SIG), this);
        #else
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_USER_CREATE_RESPONSE_FAILURE_SIG), this);
        #endif
        return;
    }

    // Create new user
    User newUser;
    newUser.id         = -1;  // Let SQLite generate the ID
    newUser.username   = evt->username;
    newUser.password   = evt->password;
    newUser.created_at = evt->created_at;
    newUser.updated_at = evt->updated_at;

    int result = user_repo_->create(newUser);

    if (result == REPO_SUCCESS)
    {
        // Post DB_USER_CREATE_RESPONSE_SUCCESS event to Auth AO
#ifdef QEVT_PAR_INIT
        DbUserCreateResponseSuccessEvt* responseEvt = Q_NEW(DbUserCreateResponseSuccessEvt, DB_USER_CREATE_RESPONSE_SUCCESS_SIG,
                                                            newUser.id, newUser.username, newUser.password, newUser.created_at, newUser.updated_at);
#else
        DbUserCreateResponseSuccessEvt* responseEvt = Q_NEW(DbUserCreateResponseSuccessEvt, DB_USER_CREATE_RESPONSE_SUCCESS_SIG);
        responseEvt->user_id                        = newUser.id;
        responseEvt->username                       = newUser.username;
        responseEvt->password                       = newUser.password;
        responseEvt->created_at                     = newUser.created_at;
        responseEvt->updated_at                     = newUser.updated_at;
#endif
        QP::QActive::PUBLISH(responseEvt, this);
    }
    else
    {
        spdlog::error("DB AO: Failed to create user, error code: {}", result);
        // Post DB_USER_CREATE_RESPONSE_FAILURE event to Auth AO
        #ifdef QEVT_PAR_INIT
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_USER_CREATE_RESPONSE_FAILURE_SIG), this);
        #else
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_USER_CREATE_RESPONSE_FAILURE_SIG), this);
        #endif
    }
}

void DbAo::processSessionCreateRequest(const DbSessionCreateRequestEvt* const evt)
{
    if (!session_repo_)
    {
        spdlog::error("DB AO: Session repository not initialized");
        // Post DB_SESSION_CREATE_RESPONSE_FAILURE event to Auth AO
        #ifdef QEVT_PAR_INIT
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_SESSION_CREATE_RESPONSE_FAILURE_SIG), this);
        #else
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_SESSION_CREATE_RESPONSE_FAILURE_SIG), this);
        #endif
        return;
    }

    // Create new session
    Session session;
    session.id         = -1;  // Let SQLite generate the ID
    session.user_id    = evt->user_id;
    session.token      = evt->token;
    session.created_at = evt->created_at;
    session.expires_at = evt->expires_at;

    int result = session_repo_->create(session);

    if (result == REPO_SUCCESS)
    {
        // Post DB_SESSION_CREATE_RESPONSE_SUCCESS event to Auth AO
#ifdef QEVT_PAR_INIT
        DbSessionCreateResponseSuccessEvt* responseEvt = Q_NEW(DbSessionCreateResponseSuccessEvt, DB_SESSION_CREATE_RESPONSE_SUCCESS_SIG,
                                                               session.id, session.user_id, session.token, session.created_at, session.expires_at);
#else
        DbSessionCreateResponseSuccessEvt* responseEvt = Q_NEW(DbSessionCreateResponseSuccessEvt, DB_SESSION_CREATE_RESPONSE_SUCCESS_SIG);
        responseEvt->session_id                        = session.id;
        responseEvt->user_id                           = session.user_id;
        responseEvt->token                             = session.token;
        responseEvt->created_at                        = session.created_at;
        responseEvt->expires_at                        = session.expires_at;
#endif
        QP::QActive::PUBLISH(responseEvt, this);
    }
    else
    {
        spdlog::error("DB AO: Failed to create session, error code: {}", result);
        // Post DB_SESSION_CREATE_RESPONSE_FAILURE event to Auth AO
        #ifdef QEVT_PAR_INIT
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_SESSION_CREATE_RESPONSE_FAILURE_SIG), this);
        #else
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_SESSION_CREATE_RESPONSE_FAILURE_SIG), this);
        #endif
    }
}

void DbAo::processSessionRemoveRequest(const DbSessionRemoveRequestEvt* const evt)
{
    if (!session_repo_)
    {
        spdlog::error("DB AO: Session repository not initialized");
        // Post DB_SESSION_REMOVE_RESPONSE_FAILURE event to Auth AO
        #ifdef QEVT_PAR_INIT
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_SESSION_REMOVE_RESPONSE_FAILURE_SIG), this);
        #else
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_SESSION_REMOVE_RESPONSE_FAILURE_SIG), this);
        #endif
        return;
    }

    // Remove session by user ID
    int result = session_repo_->remove(evt->user_id);

    if (result == REPO_SUCCESS)
    {
        // Post DB_SESSION_REMOVE_RESPONSE_SUCCESS event to Auth AO
        #ifdef QEVT_PAR_INIT
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_SESSION_REMOVE_RESPONSE_SUCCESS_SIG), this);
        #else
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_SESSION_REMOVE_RESPONSE_SUCCESS_SIG), this);
        #endif
    }
    else
    {
        spdlog::error("DB AO: Failed to remove session, error code: {}", result);
        // Post DB_SESSION_REMOVE_RESPONSE_FAILURE event to Auth AO
        #ifdef QEVT_PAR_INIT
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_SESSION_REMOVE_RESPONSE_FAILURE_SIG), this);
        #else
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_SESSION_REMOVE_RESPONSE_FAILURE_SIG), this);
        #endif
    }
}

void DbAo::processSessionFindByUserIdRequest(const DbSessionFindByUserIdRequestEvt* const evt)
{
    if (!session_repo_)
    {
        spdlog::error("DB AO: Session repository not initialized");
        // Post DB_SESSION_FIND_RESPONSE_FAILURE event to Auth AO
        #ifdef QEVT_PAR_INIT
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_SESSION_FIND_RESPONSE_FAILURE_SIG), this);
        #else
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_SESSION_FIND_RESPONSE_FAILURE_SIG), this);
        #endif
        return;
    }

    // Find session by user ID
    auto session = session_repo_->findByUserId(evt->user_id);

    if (session.has_value())
    {
        // Post DB_SESSION_FIND_RESPONSE_SUCCESS event to Auth AO
#ifdef QEVT_PAR_INIT
        DbSessionFindResponseSuccessEvt* responseEvt = Q_NEW(DbSessionFindResponseSuccessEvt, DB_SESSION_FIND_RESPONSE_SUCCESS_SIG,
                                                             session->id, session->user_id, session->token, session->created_at, session->expires_at);
#else
        DbSessionFindResponseSuccessEvt* responseEvt = Q_NEW(DbSessionFindResponseSuccessEvt, DB_SESSION_FIND_RESPONSE_SUCCESS_SIG);
        responseEvt->session_id                      = session->id;
        responseEvt->user_id                         = session->user_id;
        responseEvt->token                           = session->token;
        responseEvt->created_at                      = session->created_at;
        responseEvt->expires_at                      = session->expires_at;
#endif
        QP::QActive::PUBLISH(responseEvt, this);
    }
    else
    {
        // Post DB_SESSION_FIND_RESPONSE_FAILURE event to Auth AO
        #ifdef QEVT_PAR_INIT
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_SESSION_FIND_RESPONSE_FAILURE_SIG), this);
        #else
                QP::QActive::PUBLISH(Q_NEW(QP::QEvt, DB_SESSION_FIND_RESPONSE_FAILURE_SIG), this);
        #endif
    }
}

void DbAo::initializeDatabase()
{
    // Initialize database manager
    if (!db_manager_)
    {
        db_manager_ = new DatabaseManager("auth.db");
    }

    db_manager_->initialize();

    // Get database connection
    DatabaseConnection& db = db_manager_->getDatabaseConnection();

    // Create repositories
    user_repo_    = new UserRepository(db);
    session_repo_ = new SessionRepository(db);

    spdlog::info("DB AO: Database initialized successfully");
}